package com.meyou.javascript.rewriter;

import java.util.ArrayList;
import java.util.List;

import org.mozilla.javascript.Node;
import org.mozilla.javascript.Token;
import org.mozilla.javascript.ast.ArrayLiteral;
import org.mozilla.javascript.ast.Assignment;
import org.mozilla.javascript.ast.AstNode;
import org.mozilla.javascript.ast.Block;
import org.mozilla.javascript.ast.ConditionalExpression;
import org.mozilla.javascript.ast.ElementGet;
import org.mozilla.javascript.ast.EmptyExpression;
import org.mozilla.javascript.ast.ExpressionStatement;
import org.mozilla.javascript.ast.ForInLoop;
import org.mozilla.javascript.ast.ForLoop;
import org.mozilla.javascript.ast.FunctionCall;
import org.mozilla.javascript.ast.FunctionNode;
import org.mozilla.javascript.ast.IfStatement;
import org.mozilla.javascript.ast.InfixExpression;
import org.mozilla.javascript.ast.KeywordLiteral;
import org.mozilla.javascript.ast.Name;
import org.mozilla.javascript.ast.NumberLiteral;
import org.mozilla.javascript.ast.ObjectLiteral;
import org.mozilla.javascript.ast.ParenthesizedExpression;
import org.mozilla.javascript.ast.PropertyGet;
import org.mozilla.javascript.ast.ReturnStatement;
import org.mozilla.javascript.ast.Scope;
import org.mozilla.javascript.ast.ScriptNode;
import org.mozilla.javascript.ast.StringLiteral;
import org.mozilla.javascript.ast.UnaryExpression;
import org.mozilla.javascript.ast.VariableDeclaration;

public class JavaScriptCodeGen  {

	private int depth;
	
	public String generateCode(Node node) 
	{
		StringBuffer strBuf = new StringBuffer();
		
		generate(strBuf, node);
		return strBuf.toString();
	}
	
	protected void generate(StringBuffer strBuf, Node node)
	{
		switch(node.getType())
		{
			case Token.FOR:
				if (node instanceof ForInLoop) 
				{
					generate(strBuf, (ForInLoop) node);
				} else
				{
					generate(strBuf, (ForLoop) node);
				}
				break;
				
			case Token.RETURN:
				generate(strBuf, (ReturnStatement) node);
				break;
				
			case Token.EMPTY:
				generate(strBuf, (EmptyExpression) node);
				break;
				
			case Token.IF:
				generate(strBuf, (IfStatement) node);
				break;
				
			case Token.LP:
				generate(strBuf, (ParenthesizedExpression) node);
				break;
				
			case Token.GETELEM:
				generate(strBuf, (ElementGet) node);
				break;
				
			case Token.ASSIGN_ADD:
			case Token.ASSIGN_SUB:
			case Token.ASSIGN_MUL:
			case Token.ASSIGN_DIV:
			case Token.ASSIGN_MOD:
			case Token.ASSIGN_BITAND:
			case Token.ASSIGN_BITOR:
			case Token.ASSIGN_BITXOR:
			case Token.ASSIGN_LSH:
			case Token.ASSIGN_RSH:
			case Token.ASSIGN_URSH:
				generate(strBuf, (Assignment) node);
				break;
				
			case Token.DELPROP:
			case Token.INC:
				generate(strBuf, (UnaryExpression) node);
				break;
				
			case Token.GETPROP:
				generate(strBuf, (PropertyGet) node);
				break;
				
			case Token.ASSIGN:
				generate(strBuf, (Assignment) node);
				break;
				
			case Token.HOOK:
				generate(strBuf, (ConditionalExpression) node);
				break;
				
			case Token.ARRAYLIT:
				generate(strBuf, (ArrayLiteral) node);
				break;
				
			case Token.OBJECTLIT:
				generate(strBuf, (ObjectLiteral) node);
				break;
				
			case Token.ADD:
			case Token.MUL:
			case Token.DIV:
			case Token.SUB:
			case Token.MOD:
			case Token.LSH:
			case Token.RSH:
			case Token.URSH:
			case Token.BITAND:
			case Token.BITNOT:
			case Token.BITOR:
			case Token.BITXOR:
				generate(strBuf, (InfixExpression) node);
				break;
				
			case Token.EXPR_RESULT:
				generate(strBuf, (ExpressionStatement) node);
				break;
				
			case Token.EXPR_VOID:
				generate(strBuf, (ExpressionStatement) node);
				break;
				
			case Token.BLOCK:
				generate(strBuf, (Block) node);
				break;
				
			case Token.CALL:
				generate(strBuf, (FunctionCall) node);
				break;
				
			case Token.LOOP:
	            generate(strBuf, (ForLoop)node);
	            break;
		
		    case Token.SCRIPT:
				generate(strBuf, (ScriptNode) node);
				break;
				
		    case Token.NAME:
		    	generate(strBuf, (Name) node);
		    	break;
		    	
		    case Token.TRUE:
		    	generate(strBuf, (KeywordLiteral) node);
		    	break;
		    	
		    case Token.FALSE:
		    	generate(strBuf, (KeywordLiteral) node);
		    	break;
		    
		    case Token.THIS:
		    	generate(strBuf, (KeywordLiteral) node);
		    	break;
		    	
		    case Token.NUMBER:
		    	generate(strBuf, (NumberLiteral) node);
		    	break;
		    	
		    case Token.STRING:
		    	generate(strBuf, (StringLiteral) node);
		    	break;
		    	
		    case Token.FUNCTION:
		    	generate(strBuf, (FunctionNode) node);
		    	break;
		    	
			default:
				if (node instanceof VariableDeclaration) {
		            generate(strBuf, (VariableDeclaration)node);
		            break;
		        }
				
				throw new IllegalArgumentException("Can't generate: " + node);
		}
	}
	
	protected void generate(StringBuffer strBuf, Assignment node)
	{
		AstNode left = node.getLeft();
		generate(strBuf, left);
		int op = node.getOperator();
		if (op == Token.ASSIGN)
		{
			strBuf.append(RewriteToken.ASSIGN);
		}
		if (op == Token.ASSIGN_ADD)
		{
			strBuf.append(RewriteToken.ASSIGN_ADD);
		}
		if (op == Token.ASSIGN_SUB)
		{
			strBuf.append(RewriteToken.ASSIGN_SUB);
		}
		if (op == Token.ASSIGN_MUL)
		{
			strBuf.append(RewriteToken.ASSIGN_MUL);
		}
		if (op == Token.ASSIGN_DIV)
		{
			strBuf.append(RewriteToken.ASSIGN_DIV);
		}
		if (op == Token.ASSIGN_MOD)
		{
			strBuf.append(RewriteToken.ASSIGN_MOD);
		}
		if (op == Token.ASSIGN_BITAND)
		{
			strBuf.append(RewriteToken.ASSIGN_BITAND);
		}
		if (op == Token.ASSIGN_BITOR)
		{
			strBuf.append(RewriteToken.ASSIGN_BITOR);
		}
		if (op == Token.ASSIGN_BITXOR)
		{
			strBuf.append(RewriteToken.ASSIGN_BITXOR);
		}
		if (op == Token.ASSIGN_LSH)
		{
			strBuf.append(RewriteToken.ASSIGN_LSH);
		}
		if (op == Token.ASSIGN_RSH)
		{
			strBuf.append(RewriteToken.ASSIGN_RSH);
		}
		if (op == Token.ASSIGN_URSH)
		{
			strBuf.append(RewriteToken.ASSIGN_URSH);
		}
		AstNode right = node.getRight();
		generate(strBuf, right);
		
		if (right instanceof FunctionNode)
		{
			addNl(strBuf);
		}
		else if (right instanceof FunctionCall)
		{
			addNl(strBuf);
		}
		else
		{
			addSemi(strBuf).addNl(strBuf);
		}
	}
	
	protected void generate(StringBuffer strBuf, ConditionalExpression node)
	{
		AstNode testExpr = node.getTestExpression();
		generate(strBuf, testExpr);
		addSpace(strBuf).addCond(strBuf).addSpace(strBuf);
		AstNode trueExpr = node.getTrueExpression();
		generate(strBuf, trueExpr);
		addSpace(strBuf).addColon(strBuf).addSpace(strBuf);
		AstNode falseExpr = node.getFalseExpression();
		generate(strBuf, falseExpr);
	}
	
	protected void generate(StringBuffer strBuf, ArrayLiteral node)
	{
		strBuf.append(node.toSource());
	}
	
	protected void generate(StringBuffer strBuf, ObjectLiteral node)
	{
		strBuf.append(node.toSource());
	}
	
	protected void generate(StringBuffer strBuf, InfixExpression node)
	{
		generate(strBuf, node.getLeft());
		
		int token = node.getType();
		if (token == Token.ADD)
		{
			strBuf.append(RewriteToken.ADD);
		}
		if (token == Token.SUB)
		{
			strBuf.append(RewriteToken.SUB);
		}
		if (token == Token.MUL)
		{
			strBuf.append(RewriteToken.MUL);
		}
		if (token == Token.DIV)
		{
			strBuf.append(RewriteToken.DIV);
		}
		if (token == Token.MOD)
		{
			strBuf.append(RewriteToken.MOD);
		}
		if (token == Token.LSH)
		{
			strBuf.append(RewriteToken.LSH);
		}
		if (token == Token.RSH)
		{
			strBuf.append(RewriteToken.RSH);
		}
		if (token == Token.URSH)
		{
			strBuf.append(RewriteToken.URSH);
		}
		if (token == Token.BITAND)
		{
			strBuf.append(RewriteToken.BITAND);
		}
		if (token == Token.BITNOT)
		{
			strBuf.append(RewriteToken.BITNOT);
		}
		if (token == Token.BITOR)
		{
			strBuf.append(RewriteToken.BITOR);
		}
		if (token == Token.BITXOR)
		{
			strBuf.append(RewriteToken.BITXOR);
		}
		generate(strBuf, node.getRight());
	}
	
	protected void generate(StringBuffer strBuf, PropertyGet node)
	{
		AstNode left = node.getLeft();
		generate(strBuf, left);
		int op = node.getOperator();
		if (op == Token.GETPROP)
		{
			strBuf.append(RewriteToken.DOT);
		}
		AstNode right = node.getRight();
		generate(strBuf, right);
	}
	
	protected void generate(StringBuffer strBuf, ExpressionStatement node)
	{
		AstNode expr = node.getExpression();
		generate(strBuf, expr);
	}
	
	protected void generate(StringBuffer strBuf, Block node)
	{
		for (Node kid : node)
		{
			generate(strBuf, kid);
		}
	}
	
	protected void generate(StringBuffer strBuf, ParenthesizedExpression node)
	{
		
		addOp(strBuf);
		AstNode expr = node.getExpression();
		generate(strBuf, expr);
		addCp(strBuf);
	}
	
	protected void generate(StringBuffer strBuf, ElementGet node)
	{
		strBuf.append(node.toSource());
	}
	
	protected void generate(StringBuffer strBuf, UnaryExpression node)
	{
		int op = node.getOperator();
		if (op == Token.DELPROP) 
		{
			strBuf.append(RewriteToken.DELETE);
			addSpace(strBuf);
			AstNode operand = node.getOperand();
			generate(strBuf, operand);
			addSemi(strBuf).addNl(strBuf);
		}
		if (op == Token.INC)
		{
			strBuf.append(RewriteToken.INC);
			AstNode operand = node.getOperand();
			generate(strBuf, operand);
		}
	}
	
	protected void generate(StringBuffer strBuf, ReturnStatement node)
	{
		
		strBuf.append(RewriteToken.RETURN);
		
		AstNode returnValue = node.getReturnValue();
		if (returnValue != null)
		{
			addSpace(strBuf);
			generate(strBuf, returnValue);
		}
		addSemi(strBuf).addNl(strBuf);
	}
	
	protected void generate(StringBuffer strBuf, NumberLiteral node)
	{
		strBuf.append(node.toSource());
	}
	
	protected void generate(StringBuffer strBuf, StringLiteral node)
	{
		strBuf.append(node.toSource());
	}
	
	protected void generate(StringBuffer strBuf, Name node)
	{
		strBuf.append(node.getIdentifier());
	}
	
	protected void generate(StringBuffer strBuf, KeywordLiteral node)
	{
		if (node.getType() == Token.THIS)
		{
			strBuf.append(RewriteToken.THIS);
		}
		
		if (node.getType() == Token.TRUE)
		{
			strBuf.append(RewriteToken.TRUE);
		}
		
		if (node.getType() == Token.FALSE)
		{
			strBuf.append(RewriteToken.FALSE);
		}
	}
	
	protected void generate(StringBuffer strBuf, EmptyExpression node)
	{
	}
	
	protected void generate(StringBuffer strBuf, IfStatement node)
	{
		
		strBuf.append(RewriteToken.IF);
		addSpace(strBuf).addOp(strBuf);
		AstNode condition = node.getCondition();
		strBuf.append(condition.toSource());
		addCp(strBuf).addSpace(strBuf).addOb(strBuf).addNl(strBuf);
		
		AstNode thenPart = node.getThenPart();
		
		depth++;
		
		List<Node> kids = getPartNodes(thenPart);
		for (Node kid : kids)
		{
			setDepth(strBuf);
			generate(strBuf, kid);
		}
		
		depth--;

		AstNode elsePart = node.getElsePart();
		
		if (elsePart != null)
		{
			setDepth(strBuf);
			addCb(strBuf);
		
			addSpace(strBuf);
			strBuf.append(RewriteToken.ELSE);
			addSpace(strBuf).addOb(strBuf).addNl(strBuf);
			depth++;
			
			kids = getPartNodes(elsePart);
			
			for (Node kid : kids)
			{
				setDepth(strBuf);
				generate(strBuf, kid);
			}
			
			depth--;
		}
		
		setDepth(strBuf);
		addCb(strBuf).addSemi(strBuf).addNl(strBuf);
		
	}
	
	protected void generate(StringBuffer strBuf, FunctionCall node)
	{
		AstNode target = node.getTarget();
		generate(strBuf, target);
		List<AstNode> args = node.getArguments();
		addOp(strBuf);
		boolean first = true;
		for (AstNode arg : args)
		{
			if (!first)
				addCm(strBuf);
			generate(strBuf, arg);
			first = false;
		}
		addCp(strBuf);
		addSemi(strBuf);
		addNl(strBuf);
	}
	
	protected void generate(StringBuffer strBuf, FunctionNode node)
	{
		
		strBuf.append(RewriteToken.FUNCTION);
		if (node.getFunctionName() != null)
		{
			addSpace(strBuf);
			generate(strBuf, node.getFunctionName());
		}
		addOp(strBuf);
		boolean start = true;
		for (AstNode arg : node.getParams())
		{
			if (!start) addCm(strBuf);
			generate(strBuf, arg);
			start = false;
		}
		addCp(strBuf);
		addOb(strBuf).addNl(strBuf);
		
		depth++;
		
		Node body = node.getBody();
		
		if (body != null)
		{
			for (Node kid : body)
			{
				setDepth(strBuf);
				
				generate(strBuf, kid);
			}
		}
		
		depth--;
		
		setDepth(strBuf);
		addCb(strBuf);
	}
	
	protected void generate(StringBuffer strBuf, VariableDeclaration node)
	{
		
		strBuf.append(node.toSource());
		addSemi(strBuf).addNl(strBuf);
	}
	
	protected void generate(StringBuffer strBuf, ScriptNode node)
	{
		for (Node child : node)
		{
			generate(strBuf, child);
		}
	}
	
	protected void generate(StringBuffer strBuf, ForInLoop loop) 
	{
		
		
		strBuf.append(RewriteToken.FOR);
		if (loop.isForEach())
		{
			addSpace(strBuf);
			strBuf.append(RewriteToken.FOREACH);
		}
		
		addSpace(strBuf).addOp(strBuf);
		
		AstNode iterator = (AstNode) loop.getIterator();
		strBuf.append(iterator.toSource());
		addSpace(strBuf);
		strBuf.append(RewriteToken.IN);
		addSpace(strBuf);
		AstNode iterated = (AstNode) loop.getIteratedObject();
		strBuf.append(iterated.toSource());
		addCp(strBuf).addSpace(strBuf);

		addOb(strBuf).addNl(strBuf);
		
		depth++;
		
		Node body = loop.getBody();
		
		List<Node> kids = getPartNodes((AstNode) body);
		
		for (Node kid : kids)
		{
			setDepth(strBuf);
			generate(strBuf, kid);
		}
		
		depth--;
		
		setDepth(strBuf);
		addCb(strBuf).addSemi(strBuf).addNl(strBuf);
	}
	
	protected void generate(StringBuffer strBuf, ForLoop loop) 
	{
		strBuf.append(RewriteToken.FOR);
		
		addSpace(strBuf).addOp(strBuf);
		
		AstNode initializer = loop.getInitializer();
		strBuf.append(initializer.toSource());
		
		addSemi(strBuf);

		AstNode condition = loop.getCondition();
		strBuf.append(condition.toSource());
		
		addSemi(strBuf);
		
		AstNode increment = loop.getIncrement();
		strBuf.append(increment.toSource());
		
		addCp(strBuf).addSpace(strBuf).addOb(strBuf).addNl(strBuf);
		
		depth++;
		
		Node body = loop.getBody();
		
		List<Node> kids = getPartNodes((AstNode) body);
		
		for (Node kid : kids)
		{
			setDepth(strBuf);
			generate(strBuf, kid);
		}
		
		depth--;
		
		setDepth(strBuf);
		addCb(strBuf).addSemi(strBuf).addNl(strBuf);
	}
	
	protected JavaScriptCodeGen addSemi(StringBuffer strBuf)
	{
		strBuf.append(RewriteToken.SEMI);
		return this;
	}
	
	protected JavaScriptCodeGen addNl(StringBuffer strBuf)
	{
		strBuf.append(RewriteToken.NEWLINE);
		return this;
	}
	
	protected JavaScriptCodeGen setDepth(StringBuffer strBuf)
	{
		for (int i = 0; i < depth; i++)
		{
			addTab(strBuf);
		}
		
		return this;
	}
	
	protected JavaScriptCodeGen addTab(StringBuffer strBuf)
	{
		strBuf.append(RewriteToken.TAB_SPACE);
		return this;
	}
	
	protected JavaScriptCodeGen addColon(StringBuffer strBuf)
	{
		strBuf.append(RewriteToken.COLON);
		return this;
	}
	
	protected JavaScriptCodeGen addCond(StringBuffer strBuf)
	{
		strBuf.append(RewriteToken.COND);
		return this;
	}
	
	protected JavaScriptCodeGen addSpace(StringBuffer strBuf)
	{
		strBuf.append(RewriteToken.SPACE);
		return this;
	}
	
	protected JavaScriptCodeGen addOb(StringBuffer strBuf)
	{
		strBuf.append(RewriteToken.OPEN_BRACE);
		return this;
	}
	
	protected JavaScriptCodeGen addCb(StringBuffer strBuf)
	{
		strBuf.append(RewriteToken.CLOSE_BRACE);
		return this;
	}
	
	protected JavaScriptCodeGen addOp(StringBuffer strBuf)
	{
		strBuf.append(RewriteToken.OPEN_PAREN);
		return this;
	}
	
	protected JavaScriptCodeGen addCp(StringBuffer strBuf)
	{
		strBuf.append(RewriteToken.CLOSE_PAREN);
		return this;
	}
	
	protected JavaScriptCodeGen addCm(StringBuffer strBuf)
	{
		strBuf.append(RewriteToken.COMMA);
		return this;
	}
	
	protected List<Node> getPartNodes(AstNode node)
	{
		List<Node> parts = new ArrayList<Node>();
		
		if (node instanceof Scope)
		{
			Node part = node.getFirstChild();
			
			while (part != null) {
				parts.add(part);
				part = part.getNext();
			}
			
		}
		else
		{
			parts.add(node);
		}
		
		return parts;
	}

}
